﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using System.Data.Linq;
using System.Linq;
using System.Linq.Expressions;
using System.Web;
using System.Web.Caching;


namespace FaceSharp.MVC.Code.Extensions
{
    public static class LinqExtensions
    {

        /// <summary>
        /// Caches Linq query´s that is created for LinqToSql.
        /// Limitations are the same as SqlCacheDependency
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="q">The linq query</param>
        /// <param name="dc">Your LinqToSql DataContext</param>
        /// <param name="CacheId">The unique Id for the cache</param>
        /// <returns></returns>
        public static List<T> LinqCache<T>(this System.Linq.IQueryable<T> q, System.Data.Linq.DataContext dc, string CacheId)
        {
            var enableSqlDependency = bool.Parse(ConfigurationManager.AppSettings["EnableSqlDependency"]);
            if (!enableSqlDependency) return q.ToList();

            List<T> objCache = (List<T>)System.Web.HttpRuntime.Cache.Get(CacheId);

            if (objCache == null)
            {
                /////////No cache... implement new SqlCacheDependeny//////////
                //1. Get connstring from DataContext
                string connStr = dc.Connection.ConnectionString;
                //2. Get SqlCommand from DataContext and the LinqQuery
                string sqlCmd = dc.GetCommand(q).CommandText;

                //3. Create Conn to use in SqlCacheDependency
                using (var conn = new System.Data.SqlClient.SqlConnection(connStr))
                {
                    conn.Open();
                    //4. Create Command to use in SqlCacheDependency
                    using (var cmd = new System.Data.SqlClient.SqlCommand(sqlCmd, conn))
                    {
                        //5.0 Add all parameters provided by the Linq Query
                        foreach (System.Data.Common.DbParameter dbp in dc.GetCommand(q).Parameters)
                        {
                            cmd.Parameters.Add(new System.Data.SqlClient.SqlParameter(dbp.ParameterName, dbp.Value));
                        }
                        //5.1 Enable DB for Notifications... Only needed once per DB...
                        System.Web.Caching.SqlCacheDependencyAdmin.EnableNotifications(connStr);
                        //5.2 Get ElementType for the query

                        // need to run http://chrisbenard.net/downloads/code/AspNet_SqlCacheRegisterTableStoredProcedure_new.sql for tables with schemas
                        string NotificationTable = dc.Mapping.GetTable(q.ElementType).TableName; // q.ElementType.Name;

                        //5.3 Enable the elementtype for notification (if not done!)
                        if (!System.Web.Caching.SqlCacheDependencyAdmin.GetTablesEnabledForNotifications(connStr).Contains(NotificationTable))
                            System.Web.Caching.SqlCacheDependencyAdmin.EnableTableForNotifications(connStr, NotificationTable);
                        //6. Create SqlCacheDependency
                        System.Web.Caching.SqlCacheDependency sqldep = new System.Web.Caching.SqlCacheDependency(cmd);
                        // - removed 090506 - 7. Refresh the LinqQuery from DB so that we will not use the current Linq cache
                        // - removed 090506 - dc.Refresh(System.Data.Linq.RefreshMode.OverwriteCurrentValues, q);
                        //8. Execute SqlCacheDepency query...
                        cmd.ExecuteNonQuery();
                        //9. Execute LINQ-query to have something to cache...
                        objCache = q.ToList();
                        //10. Cache the result but use the already created objectCache. Or else the Linq-query will be executed once more...
                        System.Web.HttpRuntime.Cache.Insert(CacheId, objCache, sqldep);
                    }
                }
            }
            //Return the created (or cached) List
            return objCache;

        }


        public static List<T> LinqCache<T>(this Table<T> query) where T : class
        {
            string tableName = query.Context.Mapping.GetTable(typeof(T)).TableName;
            List<T> result = HttpRuntime.Cache[tableName] as List<T>;

            if (result == null)
            {
                using (SqlConnection cn = new SqlConnection(query.Context.Connection.ConnectionString))
                {
                    cn.Open();
                    SqlCommand cmd = new SqlCommand(query.Context.GetCommand(query).CommandText, cn);
                    cmd.Notification = null;
                    cmd.NotificationAutoEnlist = true;
                    SqlCacheDependencyAdmin.EnableNotifications(query.Context.Connection.ConnectionString);
                    if (!SqlCacheDependencyAdmin.GetTablesEnabledForNotifications(query.Context.Connection.ConnectionString).Contains(tableName))
                    {
                        SqlCacheDependencyAdmin.EnableTableForNotifications(query.Context.Connection.ConnectionString, tableName);
                    }

                    SqlCacheDependency dependency = new SqlCacheDependency(cmd);
                    cmd.ExecuteNonQuery();

                    result = query.ToList();
                    HttpRuntime.Cache.Insert(tableName, result, dependency);
                }
            }
            return result;
        }

        public static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action)
        {
            foreach (T item in enumeration)
            {
                action(item);
            }
        }

        public static TSource Set<TSource>(this TSource input,
            Action<TSource> updater)
        {
            updater(input);
            return input;
        }

        //public static IQueryable SelectMany(this IQueryable source, string selector, params object[] values)
        //{
        //    if (source == null)
        //        throw new ArgumentNullException("source");
        //    if (selector == null)
        //        throw new ArgumentNullException("selector");

        //    // Parse the lambda
        //    LambdaExpression lambda =
        //        DynamicExpression.ParseLambda(source.ElementType, null, selector, values);

        //    // Fix lambda by recreating to be of correct Func<> type in case 
        //    // the expression parsed to something other than IEnumerable<T>.
        //    // For instance, a expression evaluating to List<T> would result 
        //    // in a lambda of type Func<T, List<T>> when we need one of type
        //    // an Func<T, IEnumerable<T> in order to call SelectMany().
        //    Type inputType = source.Expression.Type.GetGenericArguments()[0];
        //    Type resultType = lambda.Body.Type.GetGenericArguments()[0];
        //    Type enumerableType = typeof(IEnumerable<>).MakeGenericType(resultType);
        //    Type delegateType = typeof(Func<,>).MakeGenericType(inputType, enumerableType);
        //    lambda = Expression.Lambda(delegateType, lambda.Body, lambda.Parameters);

        //    // Create the new query
        //    return source.Provider.CreateQuery(
        //        Expression.Call(
        //            typeof(Queryable), "SelectMany",
        //            new Type[] { source.ElementType, resultType },
        //            source.Expression, Expression.Quote(lambda)));
        //}

    }
}